package com.qfmy.core.base.service;

import com.qfmy.common.pojo.TokenVo;
import com.qfmy.common.utils.CommonUtils;
import com.qfmy.common.utils.JwtUtils;
import com.qfmy.common.utils.RedisUtils;
import com.qfmy.core.constant.CacheConstants;
import com.qfmy.core.pojo.entity.ErrorCode;
import com.qfmy.core.pojo.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 描述:
 *      防止关于token的服务方法；
 *      在此，我的初步想法是将token的相关业务操作剥离至此；
 *      例如：
 *          创建token并将token存入redis的操作；
 *          token临近过期，token续时操作；
 *      后续的操作我将逐步完成剥离，也许我的思路会是错误，但我将逐步尝试，知道最后，得到我想要的结果。
 *
 * @author zhengjt
 * @date 2022-10-12 15:58:29
 */
@Component
@Slf4j
public class TokenService {

    @Autowired
    RedisUtils redisUtils;

    private String tokenType = CommonUtils.getConfigValue("TOKEN.TYPE");

    /**
     * 创建token
     *      1）采用大小token策略
     *          小token中仅放置唯一标识uuid，用于前后校验
     *          大token中放置用户信息，存入redis，以uuid为key
     *      2）使用一个token，token放入唯一标识，redis存入用户信息和唯一标识
     *          比较唯一标识是否相同，判断token是否过期
     *
     * @return
     */
    public String createToken(TokenVo tokenVo){

        //  创建token
        Map<String, String> map = getToken(tokenVo);
        String uuid = map.get("uuid");
        String userToken = map.get("token");
        tokenVo.setUuid(uuid);

        //  Redis存放信息的key
        String tokenKey = CacheConstants.CACHE_HEARD_TOKEN + tokenVo.getUserId();
        log.info("redis key值 ： {}", tokenKey);
        log.info("存入redis的Token对象信息 ： {} ", tokenVo.toString());

        //  将token存入redis，token为key， 用户信息为value
        redisUtils.set(tokenKey, tokenVo, CacheConstants.REDIS_EXPIRE_LONG);

        return userToken;
    }

    /**
     * 刷新token
     *  1、获取uuid
     *  2、解析token，获取TokenVo对象，取得redis的key值
     *  3、获取redis中的TokenVo对象，
     *  4、比较token和redis的uuid是否相同
     *      相同： 更新token
     *      不同： 抛出异常
     * @param token 旧的token
     * @return  新的Token
     * @throws CustomException
     */
    public String refreshToken(String token) throws CustomException {

        //  新逻辑
        //  获取redis中的TokenVo对象
        TokenVo redisTokenVo = getTokenVo(token);
        String redisKey = CacheConstants.CACHE_HEARD_TOKEN + redisTokenVo.getUserId();
        //  如果一致，则生成新的token
        Map<String, String> map = getToken(redisTokenVo);
        String userToken = map.get("token");
        //  刷新redis中的token
        refreshTokenVo(redisKey, userToken);
        return  userToken;

        /// 原逻辑
        /*
        //  获取token中的唯一标识
        String uuid = JwtUtils.getTokenId(token);
        //  获取userId
        TokenVo tokenVo = JwtUtils.getTokenUser(token, tokenType);
        String userId = tokenVo.getUserId();
        //  redis的key值
        String redisKey = CacheConstants.CACHE_HEARD_TOKEN + userId;
        //  获取redis的TokenVo对象
        TokenVo redisTokenVo = (TokenVo) redisUtils.get(redisKey);

        //  比较uuid是否一致
        if (uuid.equals(redisTokenVo.getUuid())){
            //  如果一致，则生成新的token
            Map<String, String> map = getToken(tokenVo);
            String userToken = map.get("token");
            //  刷新redis中的token
            refreshTokenVo(redisKey, userToken);
            return  userToken;
        }else {
            //  不一致，抛出token失效异常
            throw new CustomException(ErrorCode.TOKEN_INVALID);
        }
         */

    }

    /**
     * 校验token是否正确
     * @param token 需要校验的token
     * @return  正确true，错误false
     * @throws CustomException
     */
    public boolean checkToken(String token) throws CustomException {
        //  校验token
        boolean checkToken = JwtUtils.checkToken(token);
        if (!checkToken){
            return false;
        }

        //  校验redis中是否一致
        TokenVo tokenVo = getTokenVo(token);
        if (tokenVo != null){
            return true;
        }

        return false;
    }

    /**
     * 获取TokenVo对象
     * @param token
     * @return
     * @throws CustomException
     */
    public TokenVo getTokenVo(String token) throws CustomException {

        //  获取token中的唯一标识
        String uuid = JwtUtils.getTokenId(token);
        //  获取userId
        TokenVo tokenVo = JwtUtils.getTokenUser(token, tokenType);
        String userId = tokenVo.getUserId();
        //  redis的key值
        String redisKey = CacheConstants.CACHE_HEARD_TOKEN + userId;

        boolean hasKey = redisUtils.hasKey(redisKey);
        if (!hasKey){
            throw new CustomException(ErrorCode.TOKEN_NEVER);
        }

        //  获取redis的TokenVo对象
        TokenVo redisTokenVo = (TokenVo) redisUtils.get(redisKey);
        //  比较uuid是否一致
        if (uuid.equals(redisTokenVo.getUuid())){
            return redisTokenVo;
        }else {
            //  不一致，抛出token失效异常
            throw new CustomException(ErrorCode.TOKEN_INVALID);
        }

    }

    /**
     * 更新redis中的TokenVo信息
     * @param redisKey  redis的key值
     * @param token     需要更新的token信息
     * @throws CustomException
     */
    private void refreshTokenVo(String redisKey, String token) throws CustomException {
        //  获取token中的唯一标识
        String uuid = JwtUtils.getTokenId(token);

        //  获取并同时移除redis中的用户信息
        TokenVo tokenVo = (TokenVo) redisUtils.getAndDelete(redisKey);
        tokenVo.setUuid(uuid);

        //  redis 存入信息
        redisUtils.set(redisKey, tokenVo, CacheConstants.REDIS_EXPIRE_LONG);
    }

    /**
     * 生成token
     * @param tokenVo 需要生成token的信息
     * @return  map
     *          < "uuid" , 唯一标识 >
     *          < "token" , 生成出来的token >
     */
    private Map<String, String> getToken(TokenVo tokenVo){

        //  生成唯一标识
        String uuid = CommonUtils.getUUID();

        //  token存放信息
        TokenVo object = new TokenVo();
        object.setUuid(uuid);
        object.setUserId(tokenVo.getUserId());

        //  生成token，userToken为用户token（用于用户前后端传输）
        String userToken = JwtUtils.createToken(object, tokenType);

        //  返回结果
        Map<String, String> result = new HashMap<>(2);
        result.put("uuid", uuid);
        result.put("token", userToken);

        return result;
    }

}
